package cn.lili.gateway.security;

import cn.hutool.json.JSONUtil;
import cn.lili.cache.Cache;
import cn.lili.cache.CachePrefix;
import cn.lili.common.security.AuthUser;
import cn.lili.common.security.context.UserContext;
import cn.lili.common.security.enums.PermissionEnum;
import cn.lili.common.security.enums.SceneEnums;
import cn.lili.common.security.enums.SecurityEnum;
import cn.lili.common.security.token.SecretKeyUtil;
import cn.lili.common.security.token.TokenUtil;
import cn.lili.common.utils.ResultUtil;
import cn.lili.gateway.utils.IgnoredUrlsProperties;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.common.utils.StringUtils;
import com.google.gson.Gson;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PatternMatchUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.Map;

/**
 * 网关初步鉴权
 *
 * @author liushuai(liushuai711 @ gmail.com)
 * @version v4.0
 * @since 2021/7/14 15:03
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class AuthFilter implements GlobalFilter, Ordered {
    /**
     * 忽略鉴权url
     */
    private final IgnoredUrlsProperties ignoredUrlsProperties;
    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    private final Cache cache;

    private final TokenUtil tokenUtil;


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        try {
            log.info("AuthFilter == filter");
            //获取请求路径
            String path = exchange.getRequest().getURI().getPath();
            ServerHttpRequest request = exchange.getRequest();

            log.info("head = {}", JSONObject.toJSONString(request.getHeaders()));
            log.info("path = {}", path);
            //判断是否需要鉴权
            if (isSkip(path) && (HttpMethod.GET.equals(exchange.getRequest().getMethod()) || path.startsWith("/auth") || HttpMethod.POST.equals(exchange.getRequest().getMethod()))) {
                return chain.filter(exchange);
            }
            //判断是否为登录请求
            ServerHttpResponse resp = exchange.getResponse();
            String headerToken = exchange.getRequest().getHeaders().getFirst(SecurityEnum.HEADER_TOKEN.getValue());
            //token 为空或者 token无效
            if (StringUtils.isEmpty(headerToken) || !this.authCheck(headerToken)) {
                //未授权返回
                log.info("112121");
                return ResultUtil.unauthorized(resp);
            }
            //判断是否有权限
            return customAuthentication(exchange, chain, headerToken);
        } catch (ExpiredJwtException eje) {
            log.info("eje,eje");
            return ResultUtil.unauthorized(exchange.getResponse());
        } catch (Exception e) {
            log.error("AuthenticationFilter-> member authentication exception:", e);
        }
        return chain.filter(exchange);
    }

    /**
     * 是否不需要鉴权，直接访问
     *
     * @param path 请求路径
     * @return 跳过/鉴权
     */
    private boolean isSkip(String path) {
        return ignoredUrlsProperties.getUrls().stream().anyMatch(pattern -> antPathMatcher.match(pattern, path));
    }

    /**
     * 鉴权 token 是否有效
     *
     * @param headerToken token
     * @return 是否鉴权通过
     */
    private boolean authCheck(String headerToken) {

        // 根据token获取用户信息
        AuthUser authUser = UserContext.getAuthUser(headerToken);
        if (authUser == null) {
            return false;
        }

        return cache.hasKey(CachePrefix.ACCESS_TOKEN.getPrefix(authUser.getScene(), authUser.getId()) + headerToken);
    }

    /**
     * 自定义权限过滤
     */
    private Mono<Void> customAuthentication(ServerWebExchange exchange, GatewayFilterChain chain, String headerToken) {
        //获取token的信息
        Claims claims = Jwts.parserBuilder().setSigningKey(SecretKeyUtil.generalKeyByDecoders()).build().parseClaimsJws(headerToken).getBody();
        //获取存储在claims中的用户信息
        String json = claims.get(SecurityEnum.USER_CONTEXT.getValue()).toString();
        AuthUser authUser = new Gson().fromJson(json, AuthUser.class);
        String path = exchange.getRequest().getURI().getPath();


        if (authUser != null && Boolean.FALSE.equals(authUser.getIsSuper()) && !authUser.getScene().equals(SceneEnums.MEMBER)) {
            //获取缓存中的权限
            Map<String, List<String>> permission =
                    (Map<String, List<String>>) this.cache.get(tokenUtil.generatePermissionCacheKey(authUser));

            if (permission == null || permission.isEmpty()) {
                return ResultUtil.forbidden(exchange.getResponse());
            }
            //获取数据(GET 请求)权限
            if (HttpMethod.GET.equals(exchange.getRequest().getMethod())) {
                //如果用户的超级权限和查阅权限都不包含当前请求的api
                if (mismatch(permission.get(PermissionEnum.SUPER.name()), path) &&
                        mismatch(permission.get(PermissionEnum.QUERY.name()), path)) {
                    log.error("当前请求路径：{},所拥有权限：{}", path, JSONUtil.toJsonStr(permission));
                    return ResultUtil.forbidden(exchange.getResponse());
                }
            //非get请求（数据操作） 判定鉴权
            } else {
                if (mismatch(permission.get(PermissionEnum.SUPER.name()), path)) {
                    log.error("当前请求路径：{},所拥有权限：{}", path, JSONUtil.toJsonStr(permission));
                    return ResultUtil.forbidden(exchange.getResponse());
                }
            }
        }
        return chain.filter(exchange);
    }

    /**
     * 校验权限
     *
     * @param permissions 权限集合
     * @param url         请求地址
     * @return 是否拥有权限
     */
    private boolean mismatch(List<String> permissions, String url) {
        if (permissions == null || permissions.isEmpty()) {
            return true;
        }
        return !PatternMatchUtils.simpleMatch(permissions.toArray(new String[0]), url);
    }

    @Override
    public int getOrder() {
        return 0;
    }
}
